package ua.kpi_java_training.zabrodskyi.module3;

/**
 * The class describes behaviour of a predator in environment.
 * 
 * @author Maksym Zabrodskyi
 * @version 25-Jun-2013
 */
public class Predator extends Animal {

	private final int MAX_STEP_OF_MOTION = 4;
	private int step;

	/**
	 * Creates a predator in a cell in environment.
	 * 
	 * @param cell
	 *            in which the predator will be created.
	 */
	public Predator(Cell cell) {
		super(cell);
	}

	/**
	 * Describes algorithm of movement of a predator in environment.
	 */
	@Override
	public void move(Environment environment) {
		if (isPossibleMove(environment)) {
			setNewCell(environment);
		}
	}

	/**
	 * Checks is possible movement of a predator.
	 * 
	 * @param environment
	 *            - environment in which the predator moves.
	 * 
	 * @return true if there is at least one step for movement, differently
	 *         false.
	 */
	private boolean isPossibleMove(Environment environment) {
		int x = this.getCell().getX();
		int y = this.getCell().getY();
		step = Random.getStep(MAX_STEP_OF_MOTION);
		return isFreeNewCell(environment, x, y)
				|| isVictimInNewCell(environment, x, y);
	}

	/**
	 * Whether check is free a new cell.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if the new cell is free, differently false.
	 */
	private boolean isFreeNewCell(Environment environment, int x, int y) {
		if (isCurrentLineEven()) {
			return isFreeNewCellFromEvenLine(environment, x, y);
		} else {
			return isFreeNewCellFromOddLine(environment, x, y);
		}
	}

	/**
	 * Check: whether the new cell in a direction from an even line is free.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if the new cell is free, differently false.
	 */
	private boolean isFreeNewCellFromEvenLine(Environment environment, int x,
			int y) {
		return environment.isFreeCell(new Cell(x + step, y))
				|| environment.isFreeCell(new Cell(x - step, y))
				|| environment.isFreeCell(new Cell(x + step / 2, y - step))
				|| environment.isFreeCell(new Cell(x + step / 2, y + step))
				|| environment
						.isFreeCell(new Cell(x - (step + 1) / 2, y - step))
				|| environment
						.isFreeCell(new Cell(x - (step + 1) / 2, y + step));
	}

	/**
	 * Check: whether the new cell in a direction from an odd line is free.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if the new cell is free, differently false.
	 */
	private boolean isFreeNewCellFromOddLine(Environment environment, int x,
			int y) {
		return environment.isFreeCell(new Cell(x + step, y))
				|| environment.isFreeCell(new Cell(x - step, y))
				|| environment.isFreeCell(new Cell(x - step / 2, y - step))
				|| environment.isFreeCell(new Cell(x - step / 2, y + step))
				|| environment
						.isFreeCell(new Cell(x + (step + 1) / 2, y - step))
				|| environment
						.isFreeCell(new Cell(x + (step + 1) / 2, y + step));
	}

	/**
	 * Check: whether there is in a new cell a victim.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if in a new cell is victim, differently false.
	 */
	private boolean isVictimInNewCell(Environment environment, int x, int y) {
		if (isCurrentLineEven()) {
			return isVictimInNewCellFromEvenLine(environment, x, y);
		} else {
			return isVictimInNewCellFromOddLine(environment, x, y);
		}
	}

	/**
	 * Check: whether there is a victim in a new cell in a direction from an
	 * even line.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if in a new cell is victim, differently false.
	 */
	private boolean isVictimInNewCellFromEvenLine(Environment environment,
			int x, int y) {
		return environment.isSettleElement(ElementType.VICTIM, new Cell(x
				+ step, y))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- step, y))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						+ step / 2, y - step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						+ step / 2, y + step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- (step + 1) / 2, y - step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- (step + 1) / 2, y + step));
	}

	/**
	 * Check: whether there is a victim in a new cell in a direction from an odd
	 * line.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 * 
	 * @return true if in a new cell is victim, differently false.
	 */
	private boolean isVictimInNewCellFromOddLine(Environment environment,
			int x, int y) {
		return environment.isSettleElement(ElementType.VICTIM, new Cell(x
				+ step, y))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- step, y))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- step / 2, y - step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						- step / 2, y + step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						+ (step + 1) / 2, y - step))
				|| environment.isSettleElement(ElementType.VICTIM, new Cell(x
						+ (step + 1) / 2, y + step));
	}

	/**
	 * Sets a new cell in a direction of the nearest victim or in a random
	 * direction.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 */
	private void setNewCell(Environment environment) {
		if (isVictimInEnvironment(environment)) {
			setNewCellToNearestVictim(environment);
		} else {
			setNewCellInRandomDirection(environment);
		}
	}

	/**
	 * Check: whether is a victim in environment.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 * 
	 * @return true if in the environment there is at least one victim,
	 *         differently false.
	 */
	private boolean isVictimInEnvironment(Environment environment) {
		return environment.getCountElements(ElementType.VICTIM) > 0;
	}

	/**
	 * Sets a new cell in a direction of the nearest victim.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 */
	private void setNewCellToNearestVictim(Environment environment) {
		Direction directionNearestVictim = environment.whereNearestElement(
				ElementType.VICTIM, this.getCell());
		Cell newCell = new Cell();
		newCell.setCell(this.getCell());
		step = 1;
		do {
			setNewCellInDirection(newCell, directionNearestVictim);
			if (newCell.isBad()
					|| environment
							.isSettleElement(ElementType.BARRIER, newCell)) {
				step--;
				setNewCellInDirection(newCell, directionNearestVictim);
				break;
			} else if (environment.isSettleElement(ElementType.VICTIM, newCell)) {
				Statistics.addMessage("Predator catch Victim!");
				break;
			}
			step++;
		} while (step <= MAX_STEP_OF_MOTION);
		this.setCell(newCell);
	}

	/**
	 * Sets a new cell in a random direction.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 */
	private void setNewCellInRandomDirection(Environment environment) {
		Cell newCell = new Cell();
		newCell.setCell(this.getCell());
		do {
			setNewCellInDirection(newCell, Random.getDirection());
		} while (newCell.isBad()
				|| environment.isSettleElement(ElementType.BARRIER, newCell));
		this.setCell(newCell);
	}

	/**
	 * Sets a new cell in a certain direction.
	 * 
	 * @param newCell
	 *            - cell which needs to be established in a direction.
	 * @param direction
	 *            - direction in which the new cell will be established.
	 */
	private void setNewCellInDirection(Cell newCell, Direction direction) {
		newCell.setCell(this.getCell());
		int x = newCell.getX();
		int y = newCell.getY();
		switch (direction) {
		case EAST:
			setNewCellInEast(newCell, x);
		case WEST:
			setNewCellInWest(newCell, x);
			break;
		case NORTH_EAST:
			setNewCellInNorthEast(newCell, x, y);
			break;
		case NORTH_WEST:
			setNewCellInNorthWest(newCell, x, y);
			break;
		case SOUTH_EAST:
			setNewCellInSouthEast(newCell, x, y);
			break;
		case SOUTH_WEST:
			setNewCellInSouthWest(newCell, x, y);
			break;
		default:
			break;
		}
	}

	/**
	 * Sets a new cell in a direction the east.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 */
	private void setNewCellInEast(Cell newCell, int x) {
		newCell.setX(x + step);
	}

	/**
	 * Sets a new cell in a direction the west.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 */
	private void setNewCellInWest(Cell newCell, int x) {
		newCell.setX(x - step);
	}

	/**
	 * Sets a new cell in a direction the north-east.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 */
	private void setNewCellInNorthEast(Cell newCell, int x, int y) {
		if (isCurrentLineEven()) {
			newCell.setX(x + step / 2);
		} else {
			newCell.setX(x + (step + 1) / 2);
		}
		newCell.setY(y - step);
	}

	/**
	 * Sets a new cell in a direction the north-west.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 */
	private void setNewCellInNorthWest(Cell newCell, int x, int y) {
		if (isCurrentLineEven()) {
			newCell.setX(x - (step + 1) / 2);
		} else {
			newCell.setX(x - step / 2);
		}
		newCell.setY(y - step);
	}

	/**
	 * Sets a new cell in a direction the south-east.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 */
	private void setNewCellInSouthEast(Cell newCell, int x, int y) {
		if (isCurrentLineEven()) {
			newCell.setX(x + step / 2);
		} else {
			newCell.setX(x + (step + 1) / 2);
		}
		newCell.setY(y + step);
	}

	/**
	 * Sets a new cell in a direction the south-west.
	 * 
	 * @param newCell
	 *            - cell which needs to be sets in a new direction.
	 * @param x
	 *            - coordinate x in which there is this predator.
	 * @param y
	 *            - coordinate y in which there is this predator.
	 */
	private void setNewCellInSouthWest(Cell newCell, int x, int y) {
		if (isCurrentLineEven()) {
			newCell.setX(x - (step + 1) / 2);
		} else {
			newCell.setX(x - step / 2);
		}
		newCell.setY(y + step);
	}

	/**
	 * Checks, whether there is current cell on an even line.
	 * 
	 * @return returns true if current cell is on an even line, differently the
	 *         false.
	 */
	private boolean isCurrentLineEven() {
		return this.getCell().getY() % 2 == 0;
	}

	/**
	 * Describes as the predator eats. If the predator moves in a cell in which
	 * there is a victim, from eats a victim. This victim removes from the
	 * environment.
	 * 
	 * @param environment
	 *            - environment in which there is a predator.
	 */
	@Override
	public void eat(Environment environment) {
		if (environment.isSettleElement(ElementType.VICTIM, this.getCell())) {
			environment.remove(ElementType.VICTIM, this.getCell());
			Statistics.countRuinedVictim();
		}
	}

	/**
	 * Describes as the predator spawns. A method empty as the technical project
	 * does not provide as a predator should spawn.
	 */
	@Override
	public void spawn(Environment environment) {
	}

	/**
	 * Returns type of an element of this copy of a class.
	 * 
	 * @return type the Predator.
	 */
	@Override
	public ElementType getType() {
		return ElementType.PREDATOR;
	}

}
